/*-
 * Copyright (c) 2003, 2004, 2006 Lev Walkin <vlm@lionet.info>.
 * All rights reserved.
 * Redistribution and modifications are permitted subject to BSD license.
 */
#include <asn_internal.h>
#include <constr_SEQUENCE_OF.h>
#include <asn_SEQUENCE_OF.h>

/*
 * The DER encoder of the SEQUENCE OF type.
 */
asn_enc_rval_t SEQUENCE_OF_encode_der(
    const asn_TYPE_descriptor_t* td, const void* ptr, int tag_mode,
    ber_tlv_tag_t tag, asn_app_consume_bytes_f* cb, void* app_key) {
  asn_TYPE_member_t* elm              = td->elements;
  const asn_anonymous_sequence_* list = _A_CSEQUENCE_FROM_VOID(ptr);
  size_t computed_size                = 0;
  ssize_t encoding_size               = 0;
  asn_enc_rval_t erval;
  int edx;

  ASN_DEBUG("Estimating size of SEQUENCE OF %s", td->name);

  /*
   * Gather the length of the underlying members sequence.
   */
  for (edx = 0; edx < list->count; edx++) {
    void* memb_ptr = list->array[edx];
    if (!memb_ptr) continue;
    erval = elm->type->op->der_encoder(elm->type, memb_ptr, 0, elm->tag, 0, 0);
    if (erval.encoded == -1) return erval;
    computed_size += erval.encoded;
  }

  /*
   * Encode the TLV for the sequence itself.
   */
  encoding_size =
      der_write_tags(td, computed_size, tag_mode, 1, tag, cb, app_key);
  if (encoding_size == -1) {
    erval.encoded       = -1;
    erval.failed_type   = td;
    erval.structure_ptr = ptr;
    return erval;
  }

  computed_size += encoding_size;
  if (!cb) {
    erval.encoded = computed_size;
    ASN__ENCODED_OK(erval);
  }

  ASN_DEBUG("Encoding members of SEQUENCE OF %s", td->name);

  /*
   * Encode all members.
   */
  for (edx = 0; edx < list->count; edx++) {
    void* memb_ptr = list->array[edx];
    if (!memb_ptr) continue;
    erval = elm->type->op->der_encoder(
        elm->type, memb_ptr, 0, elm->tag, cb, app_key);
    if (erval.encoded == -1) return erval;
    encoding_size += erval.encoded;
  }

  if (computed_size != (size_t) encoding_size) {
    /*
     * Encoded size is not equal to the computed size.
     */
    erval.encoded       = -1;
    erval.failed_type   = td;
    erval.structure_ptr = ptr;
  } else {
    erval.encoded       = computed_size;
    erval.structure_ptr = 0;
    erval.failed_type   = 0;
  }

  return erval;
}

asn_enc_rval_t SEQUENCE_OF_encode_xer(
    const asn_TYPE_descriptor_t* td, const void* sptr, int ilevel,
    enum xer_encoder_flags_e flags, asn_app_consume_bytes_f* cb,
    void* app_key) {
  asn_enc_rval_t er;
  const asn_SET_OF_specifics_t* specs =
      (const asn_SET_OF_specifics_t*) td->specifics;
  const asn_TYPE_member_t* elm        = td->elements;
  const asn_anonymous_sequence_* list = _A_CSEQUENCE_FROM_VOID(sptr);
  const char* mname                   = specs->as_XMLValueList ?
                          0 :
                          ((*elm->name) ? elm->name : elm->type->xml_tag);
  size_t mlen = mname ? strlen(mname) : 0;
  int xcan    = (flags & XER_F_CANONICAL);
  int i;

  if (!sptr) ASN__ENCODE_FAILED;

  er.encoded = 0;

  for (i = 0; i < list->count; i++) {
    asn_enc_rval_t tmper;
    void* memb_ptr = list->array[i];
    if (!memb_ptr) continue;

    if (mname) {
      if (!xcan) ASN__TEXT_INDENT(1, ilevel);
      ASN__CALLBACK3("<", 1, mname, mlen, ">", 1);
    }

    tmper = elm->type->op->xer_encoder(
        elm->type, memb_ptr, ilevel + 1, flags, cb, app_key);
    if (tmper.encoded == -1) return tmper;
    er.encoded += tmper.encoded;
    if (tmper.encoded == 0 && specs->as_XMLValueList) {
      const char* name = elm->type->xml_tag;
      size_t len       = strlen(name);
      if (!xcan) ASN__TEXT_INDENT(1, ilevel + 1);
      ASN__CALLBACK3("<", 1, name, len, "/>", 2);
    }

    if (mname) {
      ASN__CALLBACK3("</", 2, mname, mlen, ">", 1);
    }
  }

  if (!xcan) ASN__TEXT_INDENT(1, ilevel - 1);

  ASN__ENCODED_OK(er);
cb_failed:
  ASN__ENCODE_FAILED;
}

#ifndef ASN_DISABLE_PER_SUPPORT

asn_enc_rval_t SEQUENCE_OF_encode_uper(
    const asn_TYPE_descriptor_t* td, const asn_per_constraints_t* constraints,
    const void* sptr, asn_per_outp_t* po) {
  const asn_anonymous_sequence_* list;
  const asn_per_constraint_t* ct;
  asn_enc_rval_t er;
  const asn_TYPE_member_t* elm = td->elements;
  size_t encoded_edx;

  if (!sptr) ASN__ENCODE_FAILED;
  list = _A_CSEQUENCE_FROM_VOID(sptr);

  er.encoded = 0;

  ASN_DEBUG("Encoding %s as SEQUENCE OF (%d)", td->name, list->count);

  if (constraints)
    ct = &constraints->size;
  else if (td->encoding_constraints.per_constraints)
    ct = &td->encoding_constraints.per_constraints->size;
  else
    ct = 0;

  /* If extensible constraint, check if size is in root */
  if (ct) {
    int not_in_root =
        (list->count < ct->lower_bound || list->count > ct->upper_bound);
    ASN_DEBUG(
        "lb %ld ub %ld %s", ct->lower_bound, ct->upper_bound,
        ct->flags & APC_EXTENSIBLE ? "ext" : "fix");
    if (ct->flags & APC_EXTENSIBLE) {
      /* Declare whether size is in extension root */
      if (per_put_few_bits(po, not_in_root, 1)) ASN__ENCODE_FAILED;
      if (not_in_root) ct = 0;
    } else if (not_in_root && ct->effective_bits >= 0) {
      ASN__ENCODE_FAILED;
    }
  }

  if (ct && ct->effective_bits >= 0) {
    /* X.691, #19.5: No length determinant */
    if (per_put_few_bits(po, list->count - ct->lower_bound, ct->effective_bits))
      ASN__ENCODE_FAILED;
  } else if (list->count == 0) {
    /* When the list is empty add only the length determinant
     * X.691, #20.6 and #11.9.4.1
     */
    if (uper_put_length(po, 0, 0)) {
      ASN__ENCODE_FAILED;
    }
    ASN__ENCODED_OK(er);
  }

  for (encoded_edx = 0; (ssize_t) encoded_edx < list->count;) {
    ssize_t may_encode;
    size_t edx;
    int need_eom = 0;

    if (ct && ct->effective_bits >= 0) {
      may_encode = list->count;
    } else {
      may_encode = uper_put_length(po, list->count - encoded_edx, &need_eom);
      if (may_encode < 0) ASN__ENCODE_FAILED;
    }

    for (edx = encoded_edx; edx < encoded_edx + may_encode; edx++) {
      void* memb_ptr = list->array[edx];
      if (!memb_ptr) ASN__ENCODE_FAILED;
      er = elm->type->op->uper_encoder(
          elm->type, elm->encoding_constraints.per_constraints, memb_ptr, po);
      if (er.encoded == -1) ASN__ENCODE_FAILED;
    }

    if (need_eom && uper_put_length(po, 0, 0))
      ASN__ENCODE_FAILED; /* End of Message length */

    encoded_edx += may_encode;
  }

  ASN__ENCODED_OK(er);
}

asn_enc_rval_t SEQUENCE_OF_encode_aper(
    const asn_TYPE_descriptor_t* td, const asn_per_constraints_t* constraints,
    const void* sptr, asn_per_outp_t* po) {
  const asn_anonymous_sequence_* list;
  const asn_per_constraint_t* ct;
  asn_enc_rval_t er;
  asn_TYPE_member_t* elm = td->elements;
  int seq;

  if (!sptr) ASN__ENCODE_FAILED;
  list = _A_CSEQUENCE_FROM_VOID(sptr);

  er.encoded = 0;

  ASN_DEBUG(
      "Encoding %s as SEQUENCE OF size (%d) using ALIGNED PER", td->name,
      list->count);

  if (constraints)
    ct = &constraints->size;
  else if (td->encoding_constraints.per_constraints)
    ct = &td->encoding_constraints.per_constraints->size;
  else
    ct = 0;

  /* If extensible constraint, check if size is in root */
  if (ct) {
    int not_in_root =
        (list->count < ct->lower_bound || list->count > ct->upper_bound);
    ASN_DEBUG(
        "lb %ld ub %ld %s", ct->lower_bound, ct->upper_bound,
        ct->flags & APC_EXTENSIBLE ? "ext" : "fix");
    if (ct->flags & APC_EXTENSIBLE) {
      /* Declare whether size is in extension root */
      if (per_put_few_bits(po, not_in_root, 1)) ASN__ENCODE_FAILED;
      if (not_in_root) ct = 0;
    } else if (not_in_root && ct->effective_bits >= 0)
      ASN__ENCODE_FAILED;
  }

  if (ct && ct->effective_bits >= 0) {
    /* X.691, #19.5: No length determinant */
    /*		 if(per_put_few_bits(po, list->count - ct->lower_bound,
                                     ct->effective_bits))
                             ASN__ENCODE_FAILED;
    */
    if (aper_put_length(
            po, ct->upper_bound - ct->lower_bound + 1,
            list->count - ct->lower_bound) < 0)
      ASN__ENCODE_FAILED;
  }

  for (seq = -1; seq < list->count;) {
    ssize_t mayEncode;
    if (seq < 0) seq = 0;
    if (ct && ct->effective_bits >= 0) {
      mayEncode = list->count;
    } else {
      mayEncode = aper_put_length(po, -1, list->count - seq);
      if (mayEncode < 0) ASN__ENCODE_FAILED;
    }

    while (mayEncode--) {
      void* memb_ptr = list->array[seq++];
      if (!memb_ptr) ASN__ENCODE_FAILED;
      er = elm->type->op->aper_encoder(
          elm->type, elm->encoding_constraints.per_constraints, memb_ptr, po);
      if (er.encoded == -1) ASN__ENCODE_FAILED;
    }
  }

  ASN__ENCODED_OK(er);
}
#endif /* ASN_DISABLE_PER_SUPPORT */

int SEQUENCE_OF_compare(
    const asn_TYPE_descriptor_t* td, const void* aptr, const void* bptr) {
  const asn_anonymous_sequence_* a = _A_CSEQUENCE_FROM_VOID(aptr);
  const asn_anonymous_sequence_* b = _A_CSEQUENCE_FROM_VOID(bptr);
  ssize_t idx;

  if (a && b) {
    ssize_t common_length = (a->count < b->count ? a->count : b->count);
    for (idx = 0; idx < common_length; idx++) {
      int ret = td->elements->type->op->compare_struct(
          td->elements->type, a->array[idx], b->array[idx]);
      if (ret) return ret;
    }

    if (idx < b->count) /* more elements in b */
      return -1;        /* a is shorter, so put it first */
    if (idx < a->count) return 1;

  } else if (!a) {
    return -1;
  } else if (!b) {
    return 1;
  }

  return 0;
}

asn_TYPE_operation_t asn_OP_SEQUENCE_OF = {
    SEQUENCE_OF_free,
    SEQUENCE_OF_print,
    SEQUENCE_OF_compare,
    SEQUENCE_OF_decode_ber,
    SEQUENCE_OF_encode_der,
    SEQUENCE_OF_decode_xer,
    SEQUENCE_OF_encode_xer,
#ifdef ASN_DISABLE_OER_SUPPORT
    0,
    0,
#else
    SEQUENCE_OF_decode_oer,  /* Same as SET OF decoder. */
    SEQUENCE_OF_encode_oer,  /* Same as SET OF encoder */
#endif /* ASN_DISABLE_OER_SUPPORT */
#ifdef ASN_DISABLE_PER_SUPPORT
    0,
    0,
    0,
    0,
#else
    SEQUENCE_OF_decode_uper, /* Same as SET OF decoder */
    SEQUENCE_OF_encode_uper, SEQUENCE_OF_decode_aper,
    SEQUENCE_OF_encode_aper,
#endif /* ASN_DISABLE_PER_SUPPORT */
    SEQUENCE_OF_random_fill,
    0 /* Use generic outmost tag fetcher */
};
